#!/bin/bash

set -x

SYS_ID="$(cat /sys/devices/virtual/dmi/id/product_name)"

declare -r CLIENT=$1
declare -r CLIENT_CONFIG_DIR_USR=/usr/share/gamescope-session-plus/sessions.d
declare -r CLIENT_CONFIG_DIR_ETC=/etc/gamescope-session-plus/sessions.d
declare -r CLIENT_CONFIG_DIR_LOCAL="${XDG_CONFIG_HOME:-$HOME/.config}/gamescope-session-plus/sessions.d"

# default empty session recovery function
short_session_recover() {
	:
}

# default empty function to run after gamescope is started
post_gamescope_start() {
	:
}

# default empty function to run after client exits
post_client_shutdown() {
	:
}

get_gamescope_binary() {
	if [ -n "$GAMESCOPE_BIN" ]; then
		echo $GAMESCOPE_BIN
	else
		echo "/usr/bin/gamescope"
	fi
}

gamescope_has_option() {
	if ($(get_gamescope_binary) --help 2>&1 | grep -e "$1" > /dev/null); then
		return 0
	fi

	return 1
}

# Fix intel color corruption
# might come with some performance degradation but is better than a corrupted
# color image
export INTEL_DEBUG=norbc
export mesa_glthread=true

# This should be used by default by gamescope. Cannot hurt to force it anyway.
# Reported better framelimiting with this enabled
export ENABLE_GAMESCOPE_WSI=1

# Force Qt applications to run under xwayland
export QT_QPA_PLATFORM=xcb

# Some environment variables by default (taken from Deck session)
export SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0

# Enable Mangoapp
export MANGOHUD_CONFIGFILE=$(mktemp /tmp/mangohud.XXXXXXXX)

export RADV_FORCE_VRS_CONFIG_FILE=$(mktemp /tmp/radv_vrs.XXXXXXXX)

# Plop GAMESCOPE_MODE_SAVE_FILE into $XDG_CONFIG_HOME (defaults to ~/.config).
export GAMESCOPE_MODE_SAVE_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/modes.cfg"
export GAMESCOPE_PATCHED_EDID_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/edid.bin"

# Make path to gamescope mode save file.
mkdir -p "$(dirname "$GAMESCOPE_MODE_SAVE_FILE")"
touch "$GAMESCOPE_MODE_SAVE_FILE"
#echo "Making Gamescope Mode Save file at \"$GAMESCOPE_MODE_SAVE_FILE\""

# Make path to Gamescope edid patched file.
mkdir -p "$(dirname "$GAMESCOPE_PATCHED_EDID_FILE")"
touch "$GAMESCOPE_PATCHED_EDID_FILE"
#echo "Making Gamescope patched edid at \"$GAMESCOPE_PATCHED_EDID_FILE\""

# There is no way to set a color space for an NV12
# buffer in Wayland. And the color management protocol that is
# meant to let this happen is missing the color range...
# So just workaround this with an ENV var that Remote Play Together
# and Gamescope will use for now.
export GAMESCOPE_NV12_COLORSPACE=k_EStreamColorspace_BT601

# Workaround older versions of vkd3d-proton setting this
# too low (desc.BufferCount), resulting in symptoms that are potentially like
# swapchain starvation.
export VKD3D_SWAPCHAIN_LATENCY_FRAMES=3

# Initially write no_display to our config file
# so we don't get mangoapp showing up before Steam initializes
# on OOBE and stuff.
mkdir -p "$(dirname "$MANGOHUD_CONFIGFILE")"
echo "no_display" >"$MANGOHUD_CONFIGFILE"

# Prepare our initial VRS config file
# for dynamic VRS in Mesa.
mkdir -p "$(dirname "$RADV_FORCE_VRS_CONFIG_FILE")"
echo "1x1" >"$RADV_FORCE_VRS_CONFIG_FILE"

# To expose vram info from radv
export WINEDLLOVERRIDES=dxgi=n

# Don't wait for buffers to idle on the client side before sending them to gamescope
export vk_xwayland_wait_ready=false

# To play nice with the short term callback-based limiter for now
export GAMESCOPE_LIMITER_FILE=$(mktemp /tmp/gamescope-limiter.XXXXXXXX)

# Temporary crutch until dummy plane interactions / etc are figured out
export GAMESCOPE_DISABLE_ASYNC_FLIPS=1

ulimit -n 524288

# Source device quirks if exists
if [ -f /usr/share/gamescope-session-plus/device-quirks ]; then
	. /usr/share/gamescope-session-plus/device-quirks
fi

# Source client app specific configuration files
set -a
for i in ${CLIENT_CONFIG_DIR_USR}/${CLIENT} ${CLIENT_CONFIG_DIR_ETC}/${CLIENT} ${CLIENT_CONFIG_DIR_LOCAL}/${CLIENT}; do
	[[ -f "${i}" ]] && . "${i}"
done
set +a

# Source user configuration from ~/.config/environment.d
set -a
for i in "${HOME}"/.config/environment.d/*.conf; do
	[[ -f "${i}" ]] && . "${i}"
done
set +a

# Setup socket for gamescope
# Create run directory file for startup and stats sockets
tmpdir="$([[ -n ${XDG_RUNTIME_DIR+x} ]] && mktemp -p "$XDG_RUNTIME_DIR" -d -t gamescope.XXXXXXX)"
socket="${tmpdir:+$tmpdir/startup.socket}"
stats="${tmpdir:+$tmpdir/stats.pipe}"
# Fail early if we don't have a proper runtime directory setup
if [[ -z $tmpdir || -z ${XDG_RUNTIME_DIR+x} ]]; then
	echo >&2 "!! Failed to find run directory in which to create stats session sockets (is \$XDG_RUNTIME_DIR set?)"
	exit 0
fi

export GAMESCOPE_STATS="$stats"
mkfifo -- "$stats"
mkfifo -- "$socket"

# Attempt to claim global session if we're the first one running (e.g. /run/1000/gamescope)
linkname="gamescope-stats"
#   shellcheck disable=SC2031 # (broken warning)
sessionlink="${XDG_RUNTIME_DIR:+$XDG_RUNTIME_DIR/}${linkname}" # Account for XDG_RUNTIME_DIR="" (notfragileatall)
lockfile="$sessionlink".lck
exec 9>"$lockfile" # Keep as an fd such that the lock lasts as long as the session if it is taken
if flock -n 9 && rm -f "$sessionlink" && ln -sf "$tmpdir" "$sessionlink"; then
	# Took the lock.  Don't blow up if those commands fail, though.
	echo >&2 "Claimed global gamescope stats session at \"$sessionlink\""
else
	echo >&2 "!! Failed to claim global gamescope stats session"
fi

if [ -z "$GAMESCOPECMD" ]; then
	# Set default values
	: "${OUTPUT_CONNECTOR:=*,eDP-1}"
	: "${XWAYLAND_COUNT:=2}"
	: "${TOUCH_MODE:=4}"
	: "${HIDE_CURSOR_DELAY_MS:=3000}"
	: "${FADE_OUT_DURATION_MS:=200}"

	CURSOR=""
	if [ -f "$CURSOR_FILE" ]; then
		# Use specified cursor if file exists
		CURSOR="--cursor ${CURSOR_FILE}"
	fi

	RESOLUTION=""
	if [ -n "$SCREEN_WIDTH" ] && [ -n "$SCREEN_HEIGHT" ]; then
		RESOLUTION="-W $SCREEN_WIDTH -H $SCREEN_HEIGHT"
	fi

	INTERNAL_RESOLUTION=""
	if [ -n "$INTERNAL_WIDTH" ] && [ -n "$INTERNAL_HEIGHT" ] ; then
		INTERNAL_RESOLUTION="-w $INTERNAL_WIDTH -h $INTERNAL_HEIGHT"
	fi

	DRM_MODE_OPTION=""
	if [ -n "$DRM_MODE" ] ; then
		DRM_MODE_OPTION="--generate-drm-mode $DRM_MODE"
	fi

	ORIENTATION_OPTION=""
	if [ -n "$ORIENTATION" ] ; then
		ORIENTATION_OPTION="--force-orientation $ORIENTATION"
	fi

	ADAPTIVE_SYNC_OPTION=""
	if [ -n "$ADAPTIVE_SYNC" ]; then
		ADAPTIVE_SYNC_OPTION="--adaptive-sync"
	fi

	PANEL_TYPE_OPTION=""
	if [ -n "$PANEL_TYPE" ] && gamescope_has_option "--force-panel-type"; then
		PANEL_TYPE_OPTION="--force-panel-type $PANEL_TYPE"
	fi

	CUSTOM_REFRESH_RATES_OPTION=""
	if [ -n "$CUSTOM_REFRESH_RATES" ] && gamescope_has_option "--custom-refresh-rates"; then
		CUSTOM_REFRESH_RATES_OPTION="--custom-refresh-rates $CUSTOM_REFRESH_RATES"
	fi

	USE_ROTATION_SHADER_OPTION=""
	if [ -n "$USE_ROTATION_SHADER" ] && gamescope_has_option "--use-rotation-shader"; then
		USE_ROTATION_SHADER_OPTION="--use-rotation-shader $USE_ROTATION_SHADER"
	fi

	BACKEND_OPTION=""
	# Check if an older GPU that needs the SDL workaround is in use.
	if [ -n "$BACKEND" ] && gamescope_has_option "--backend"; then
		BACKEND_OPTION="--backend $BACKEND"
	fi

  HDR_OPTIONS=""
  if [ "$ENABLE_GAMESCOPE_HDR" == "1" ] && [ "$ENABLE_GAMESCOPE_WSI" == "1" ] && gamescope_has_option "--hdr-enabled" && gamescope_has_option "--hdr-itm-enable"; then
    HDR_OPTIONS="--hdr-enabled --hdr-itm-enable"
    export ENABLE_HDR_WSI=1
    export DXVK_HDR=1
  fi

  if [ -n "$GAMESCOPE_HDR_NITS" ] && gamescope_has_option "--hdr-itm-sdr-nits" && gamescope_has_option "--hdr-sdr-content-nits"; then
    HDR_OPTIONS="$HDR_OPTIONS --hdr-itm-sdr-nits $GAMESCOPE_HDR_NITS --hdr-sdr-content-nits $GAMESCOPE_HDR_NITS"
  fi

	GAMESCOPECMD="$(get_gamescope_binary) \
		$CURSOR \
		$RESOLUTION \
		$INTERNAL_RESOLUTION \
		$ORIENTATION_OPTION \
		$DRM_MODE_OPTION \
		$ADAPTIVE_SYNC_OPTION \
		$PANEL_TYPE_OPTION \
		$CUSTOM_REFRESH_RATES_OPTION \
		$USE_ROTATION_SHADER_OPTION \
		$BACKEND_OPTION \
		$HDR_OPTIONS \
		--prefer-output $OUTPUT_CONNECTOR \
		--xwayland-count $XWAYLAND_COUNT \
		--default-touch-mode $TOUCH_MODE \
		--hide-cursor-delay $HIDE_CURSOR_DELAY_MS \
		--fade-out-duration $FADE_OUT_DURATION_MS \
		--steam"
fi

# Add socket and stats read
GAMESCOPECMD+=" -R $socket -T $stats"

# Add custom vulkan adapter if specified
if [ -n "$VULKAN_ADAPTER" ]; then
	GAMESCOPECMD+=" --prefer-vk-device $VULKAN_ADAPTER"
fi

# Attempt to fallback to a desktop session if something goes wrong too many times
short_session_tracker_file="/tmp/chimeraos-short-session-tracker"
short_session_duration=60
short_session_count_before_reset=5
SECONDS=0

short_session_count=$(wc <"$short_session_tracker_file" -l)

if [[ "$short_session_count" -ge "$short_session_count_before_reset" ]]; then
	echo >&2 "gamescope-session-plus: detected broken ${CLIENT} or gamescope failure, will try to reset the session"

	short_session_recover

	# rearm
	rm "$short_session_tracker_file"
	exit 1
fi

# Start gamescope compositor and background it
$GAMESCOPECMD &
gamescope_pid="$!"

if read -r -t 5 response_x_display response_wl_display <>"$socket"; then
	export DISPLAY="$response_x_display"
	export GAMESCOPE_WAYLAND_DISPLAY="$response_wl_display"
	# We're done!
else
	echo "gamescope failed" >>"$short_session_tracker_file"
	kill -9 "$gamescope_pid"
	wait -n "$gamescope_pid"
	exit 1
	# Systemd or Session manager will have to restart session
fi

# Run session defined hook
post_gamescope_start

# Input method support if present
if command -v /usr/bin/ibus-daemon >/dev/null; then
	/usr/bin/ibus-daemon -d -r --panel=disable --emoji-extension=disable
fi

# If we have mangoapp binary start it
MANGOAPP_CONFIGFILE=$(mktemp /tmp/mangoapp.XXXXXXXX)
ln -sf $MANGOHUD_CONFIGFILE $MANGOAPP_CONFIGFILE
if command -v mangoapp >/dev/null; then
	(while :; do
		MANGOHUD_CONFIGFILE=$MANGOAPP_CONFIGFILE mangoapp
	done) &
fi

# Handle Galileo Mura Correction
# This is applied here and not in device-quirks because it must be called after gamescope has started.
if [[ ":Galileo:" =~ ":$SYS_ID:"  ]]; then
  if command -v galileo-mura-setup > /dev/null; then
    galileo-mura-setup &
  fi
fi

# For compatibility with older user configuration overrides
if [ -n "$STEAMCMD" ]; then
	CLIENTCMD=$STEAMCMD
fi

# Start client application
$CLIENTCMD

if [[ "$SECONDS" -lt "$short_session_duration" ]]; then
	echo "${CLIENT} failed" >>"$short_session_tracker_file"
else
	rm "$short_session_tracker_file"
fi

post_client_shutdown

# When the client exits, kill gamescope nicely
kill $gamescope_pid

# Start a background sleep for five seconds because we don't trust it
sleep 5 &
sleep_pid="$!"

# Catch reboot and powerof sentinels here
if [[ -e "$REBOOT_SENTINEL" ]]; then
	rm -f "$REBOOT_SENTINEL"
	# rearm short session tracker
	rm "$short_session_tracker_file"
	reboot
fi
if [[ -e "$SHUTDOWN_SENTINEL" ]]; then
	rm -f "$SHUTDOWN_SENTINEL"
	# rearm short session tracker
	rm "$short_session_tracker_file"
	poweroff
fi

# Wait for gamescope or the sleep to finish for timeout purposes
ret=0
wait -n $gamescope_pid $sleep_pid || ret=$?

# If we get a SIGTERM/etc while waiting this happens.  Proceed to kill -9 everything but complain
if [[ $ret = 127 ]]; then
	echo >&2 "gamescope-session-plus: Interrupted while waiting on teardown, force-killing remaining tasks"
fi

# Kill all remaining jobs and warn if unexpected things are in there (should be just sleep_pid, unless gamescope failed
# to exit in time or we hit the interrupt case above)
for job in $(jobs -p); do
	# Warn about unexpected things
	if [[ $ret != 127 && $job = "$gamescope_pid" ]]; then
		echo >&2 "gamescope-session-plus: gamescope timed out while exiting, killing"
	elif [[ $ret != 127 && $job != "$sleep_pid" ]]; then
		echo >&2 "gamescope-session-plus: unexpected background pid $job at teardown: "
		# spew some debug about it
		ps -p "$job" >&2
	fi
	kill -9 "$job"
done
